package com.dcy.system.filter.web.filter;

import cn.hutool.core.util.PrimitiveArrayUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.dcy.system.filter.wrapper.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * 全局访问性能拦截
 *
 * @author YuChangjiang
 */
@Slf4j(topic = "requist-http")
public class LogWebInvokeTimeFilter extends BaseLogFilter {

    private final TransmittableThreadLocal<StopWatch> invokeTimeTL = new TransmittableThreadLocal<>();

    /**
     * 默认限制 5KB
     */
    private static int maxPayloadLength = 1024 * 5;

    public static void setMaxPayloadLength(int length) {
        Assert.isTrue(length >= 0, "'length' should be larger than or equal to 0");
        maxPayloadLength = length;
    }

    @Override
    protected void beforeRequest(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        invokeTimeTL.set(stopWatch);

        final String url = this.getRequestUrl(request);
        // 打印请求参数
        this.printRequestBody(url, request);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        final String url = this.getRequestUrl(request);
        StopWatch stopWatch = invokeTimeTL.get();
        stopWatch.stop();
        this.printResponseBody(url, response, stopWatch.getTime());
        invokeTimeTL.remove();
    }

    private void printRequestBody(String url, HttpServletRequest request) throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        sb.append("请求信息：");
        sb.append("\nIP:\t\t").append(ServletUtil.getClientIP(request));
        sb.append("\nURL:\t").append(url);
        sb.append("\n入参:\t");

        String method = request.getMethod();
        if ("POST".equalsIgnoreCase(method) &&
                (isJsonRequest(request.getContentType()) || isXmlRequest(request.getContentType()))) {
            RequestWrapper requestWrapper = WebUtils.getNativeRequest(request, RequestWrapper.class);
            if (Objects.nonNull(requestWrapper)) {
                String bodyParam = requestWrapper.getBodyString();
                if (StringUtils.isNotBlank(bodyParam)) {
                    sb.append(bodyParam);
                }
            }
        }

        log.info(sb.toString());
    }

    private void printResponseBody(String url, HttpServletResponse response, long time) {
        StringBuilder sb = new StringBuilder();
        sb.append("响应信息：");
        sb.append("\n耗时:\t").append(time).append("ms");
        sb.append("\nURL:\t").append(url);
        sb.append("\n出参:\t");
        ContentCachingResponseWrapper wrappedResponse = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (Objects.nonNull(wrappedResponse)) {
            String bodyParam = null;
            if (isJsonRequest(wrappedResponse.getContentType()) || isXmlRequest(wrappedResponse.getContentType())) {
                byte[] bytes = wrappedResponse.getContentAsByteArray();
                if (maxPayloadLength < bytes.length) {
                    // 限制输出内容
                    log.debug("出参内容大于{}KB，将被截断输出", (maxPayloadLength / 1024));
                    bytes = PrimitiveArrayUtil.sub(bytes, 0, maxPayloadLength);
                }
                bodyParam = ArrayUtils.isNotEmpty(bytes) ? StringUtils.toEncodedString(bytes, StandardCharsets.UTF_8) : "";
            }
            if (StringUtils.isNotBlank(bodyParam)) {
                sb.append(bodyParam);
            }
        }
        log.info(sb.toString());
    }

    /**
     * 判断数据类型是否为json
     *
     * @param contentType content type
     * @return boolean
     */
    private boolean isJsonRequest(String contentType) {
        if (StringUtils.isNotEmpty(contentType)) {
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
        }
        return false;
    }

    /**
     * 判断数据类型是否为xml
     *
     * @param contentType content type
     * @return boolean
     */
    private boolean isXmlRequest(String contentType) {
        if (StringUtils.isNotEmpty(contentType)) {
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_XML_VALUE) ||
                    StringUtils.startsWithIgnoreCase(contentType, MediaType.TEXT_XML_VALUE);
        }
        return false;
    }

    private String getRequestUrl(HttpServletRequest request) throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getMethod()).append(" ").append(request.getRequestURI());
        String requestQuery = request.getQueryString();
        if (StringUtils.isNotBlank(requestQuery)) {
            sb.append("?").append(URLDecoder.decode(requestQuery, "utf-8"));
        }
        return sb.toString();
    }
}
